﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace Pfz.Remoting
{
	/// <summary>
	/// TcpListener that implements IAdvancedDisposable and IListener interfaces.
	/// It also disposes all its active connections when it is itself disposed.
	/// </summary>
	public sealed class DisposableTcpListener:
		IConnectionListener<DisposableTcpConnection>
	{
		internal readonly object _lock = new object();
		internal HashSet<DisposableTcpConnection> _clients = new HashSet<DisposableTcpConnection>();
		private TcpListener _listener;
		private readonly bool _hasTcpDelay;
		private readonly int _receiveBufferSize;
		private readonly int _sendBufferSize;
		private readonly int _bufferedWriteStreamSize;
		/// <summary>
		/// Creates and configures a new DisposableTcpListener.
		/// </summary>
		/// <param name="address">The addess to listen.</param>
		/// <param name="port">The port to listen.</param>
		/// <param name="hasTcpDelay">If true, the TcpDelay (to avoid multiple small packets) is on.</param>
		/// <param name="bufferedWriteStreamSize">If different than -1 tells that a local BufferedWriteStream should be used by new connections.</param>
		/// <param name="receiveBufferSize">If different than -1 configures the Tcp Receive Buffer size.</param>
		/// <param name="sendBufferSize">If different than -1 configures the Tcp Send Buffer size.</param>
		public DisposableTcpListener(IPAddress address, int port, bool hasTcpDelay=true, int bufferedWriteStreamSize=-1, int receiveBufferSize=-1, int sendBufferSize=-1)
		{
			_hasTcpDelay = hasTcpDelay;
			_receiveBufferSize = receiveBufferSize;
			_sendBufferSize = sendBufferSize;
			_bufferedWriteStreamSize = bufferedWriteStreamSize;

			_listener = new TcpListener(address, port);
			_listener.Start();
		}

		/// <summary>
		/// Frees the internal listener and disposes all active clients.
		/// </summary>
		public void Dispose()
		{
			lock(_lock)
			{
				var listener = _listener;
				if (listener != null)
				{
					_listener = null;
					listener.Stop();
				}

				var clients = _clients;
				if (clients != null)
				{
					_clients = null;

					foreach(var client in clients)
						client.Dispose();
				}
			}
		}

		/// <summary>
		/// Gets a value indicating if this listener was disposed.
		/// </summary>
		public bool WasDisposed
		{
			get
			{
				return _listener == null;
			}
		}

		/// <summary>
		/// Tries to accept a new connection.
		/// </summary>
		public DisposableTcpConnection TryAccept()
		{
			try
			{
				var client = _listener.AcceptTcpClient();
				if (client == null)
					return null;

				try
				{
					client.NoDelay = !_hasTcpDelay;

					if (_receiveBufferSize != -1)
						client.ReceiveBufferSize = _receiveBufferSize;

					if (_sendBufferSize != -1)
						client.SendBufferSize = _sendBufferSize;

					Stream stream = client.GetStream();
					if (_bufferedWriteStreamSize != -1)
						stream = new BufferedWriteStream(stream, _bufferedWriteStreamSize);

					return new DisposableTcpConnection(this, client, stream);
				}
				catch
				{
					client.Close();
					throw;
				}
			}
			catch
			{
				if (!WasDisposed)
					throw;

				return null;
			}
		}

		/// <summary>
		/// Gets a copy array of all connected clients.
		/// </summary>
		public DisposableTcpConnection[] GetConnections()
		{
			lock(_lock)
			{
				var clients = _clients;
				if (clients == null)
					return null;

				return clients.ToArray();
			}
		}
	}
}
